using BepuUtilities;
using DemoContentLoader;
using SharpDX.Direct3D11;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
#if MYCODE
using BepuUtilities.Vectors;
#else
using System.Numerics;
#endif
namespace DemoRenderer.UI
{
    /// <summary>
    /// 渲染单个字符字形实例的GPU相关信息。
    /// </summary>
    public struct GlyphInstance
    {
        /// <summary>
        /// 字形最小角的压缩位置。低16位为X,高16位为Y。应按PackedToScreen进行缩放。
        /// </summary>
        public uint PackedMinimum;
        /// <summary>
        /// 字形使用的填充水平轴。较低的16位是X,较高的16位是Y。从0处的-1.0%到65534处的1.0%的范围内打包。
        /// </summary>
        public uint PackedHorizontalAxis;
        /// <summary>
        /// 两个属性的组合：缩放以应用于源字形。UNRM在65535处的0到16.0的范围内打包,存储在较低的16位中,
        /// 以及存储在高16位的字体中的字形类型的ID。
        /// </summary>
        public uint PackedScaleAndSourceId;
        /// <summary>
        /// RGBA颜色,以unrm方式打包,位0到7是R,位8到15是G,位16到23是B,位24到31是A。
        /// </summary>
        public uint PackedColor;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public GlyphInstance(ref Vector2 start, ref Vector2 horizontalAxis, float scale, int sourceId, ref Vector4 color, ref Vector2 screenToPackedScale)
        {
            // 请注意,如果位置在目标范围之外,这可能会做一些奇怪的事情。为了演示的缘故,我们假设一切都在框架内。
            // 如果您想在不能保证所有内容都在框架内的游戏中使用此选项,则需要修改此包装范围。
            // 一个简单的选项是只将映射区域设置为延伸到渲染目标之外。它会稍微降低精度密度,但这并不太重要。
            PackedMinimum = (uint)(start.X * screenToPackedScale.X) | ((uint)(start.Y * screenToPackedScale.Y) << 16);
            var scaledAxisX = (uint)(horizontalAxis.X * 32767f + 32767f);
            var scaledAxisY = (uint)(horizontalAxis.Y * 32767f + 32767f);
            Debug.Assert(scaledAxisX <= 65534);
            Debug.Assert(scaledAxisY <= 65534);
            PackedHorizontalAxis = scaledAxisX | (scaledAxisY << 16);
            var packScaledScale = scale * (65535f / 16f);
            Debug.Assert(packScaledScale >= 0);
            if (packScaledScale > 65535f)
                packScaledScale = 65535f;
            Debug.Assert(sourceId >= 0 && sourceId < 65536);
            PackedScaleAndSourceId = (uint)packScaledScale | (uint)(sourceId << 16);
            PackedColor = Helpers.PackColor(color);
        }
    }

    public class GlyphRenderer : IDisposable
    {
        struct VertexConstants
        {
            public Vector2 PackedToScreenScale;
            public Vector2 ScreenToNDCScale;
            public Vector2 InverseAtlasResolution;
        }
        ConstantsBuffer<VertexConstants> vertexConstants;

        StructuredBuffer<GlyphInstance> instances;
        IndexBuffer indices;

        SamplerState sampler;
        VertexShader vertexShader;
        PixelShader pixelShader;
        public GlyphRenderer(Device device, ShaderCache cache, int maximumGlyphsPerDraw = 2048)
        {
            instances = new StructuredBuffer<GlyphInstance>(device, maximumGlyphsPerDraw, "Glyph Instances");
            indices = new IndexBuffer(Helpers.GetQuadIndices(maximumGlyphsPerDraw), device, "Glyph Indices");

            var samplerDescription = SamplerStateDescription.Default();
            samplerDescription.Filter = Filter.MinMagMipLinear;
            sampler = new SamplerState(device, samplerDescription);

            vertexConstants = new ConstantsBuffer<VertexConstants>(device, debugName: "Glyph Renderer Vertex Constants");

            vertexShader = new VertexShader(device, cache.GetShader(@"UI\RenderGlyphs.hlsl.vshader"));
            pixelShader = new PixelShader(device, cache.GetShader(@"UI\RenderGlyphs.hlsl.pshader"));
        }

        /// <summary>
        /// 使用可以在使用GlyphRenderer.Render函数绘制的所有字形批次之间共享的任何字形渲染特定渲染状态设置渲染管道。
        /// </summary>
        /// <param name="context">要配置的上下文。</param>
        public void PreparePipeline(DeviceContext context)
        {
            // 这假设已经为屏幕空间透明渲染适当地设置了光栅化器、混合和深度状态。
            context.InputAssembler.InputLayout = null;
            context.InputAssembler.SetIndexBuffer(indices);
            context.InputAssembler.PrimitiveTopology = SharpDX.Direct3D.PrimitiveTopology.TriangleList;
            context.VertexShader.Set(vertexShader);
            context.VertexShader.SetConstantBuffer(0, vertexConstants.Buffer);
            context.VertexShader.SetShaderResource(0, instances.SRV);
            context.PixelShader.Set(pixelShader);
            context.PixelShader.SetSampler(0, sampler);
        }

        public void Render(DeviceContext context, Font font, Int2 screenResolution, Span<GlyphInstance> glyphs)
        {
            var vertexConstantsData = new VertexConstants
            {
                // 前两个刻度可以上传一次,但需要另一个缓冲区。还不够重要。
                // 打包的最小值必须允许亚像素位置。因此,将范围0到65535分布在像素范围0到分辨率上。
                PackedToScreenScale = new Vector2(screenResolution.X / 65535f, screenResolution.Y / 65535f),
                ScreenToNDCScale = new Vector2(2f / screenResolution.X, -2f / screenResolution.Y),
                InverseAtlasResolution = new Vector2(1f / font.Content.Atlas.Width, 1f / font.Content.Atlas.Height)
            };
            vertexConstants.Update(context, ref vertexConstantsData);
            context.VertexShader.SetShaderResource(1, font.Sources.SRV);
            context.PixelShader.SetShaderResource(0, font.AtlasSRV);

            var count = glyphs.Length;
            var start = 0;
            while (count > 0)
            {
                var batchCount = Math.Min(instances.Capacity, count);
                instances.Update(context, glyphs.Slice(start, batchCount));
                context.DrawIndexed(batchCount * 6, 0, 0);
                count -= batchCount;
                start += batchCount;
            }
        }
        bool disposed;
        public void Dispose()
        {
            if (!disposed)
            {
                disposed = true;
                vertexShader.Dispose();
                pixelShader.Dispose();
                instances.Dispose();
                indices.Dispose();
                sampler.Dispose();
                vertexConstants.Dispose();
            }
        }

#if DEBUG
        ~GlyphRenderer()
        {
            Helpers.CheckForUndisposed(disposed, this);
        }
#endif
    }
}
